﻿// ****************************************************************************
// <copyright file="WeakFunc.cs" company="GalaSoft Laurent Bugnion">
// Copyright © GalaSoft Laurent Bugnion 2012-2013
// </copyright>
// ****************************************************************************
// <author>Laurent Bugnion</author>
// <email>laurent@galasoft.ch</email>
// <date>15.1.2012</date>
// <project>GalaSoft.MvvmLight</project>
// <web>http://www.galasoft.ch</web>
// <license>
// See license.txt in this solution or http://www.galasoft.ch/license_MIT.txt
// </license>
// ****************************************************************************
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace TimersXP.Helpers
{
    /// <summary>Stores a Func&ltT&gt without causing a hard reference to be created to the Func's owner. The owner can be garbage collected at any time.</summary>
    /// <typeparam name="TResult">The type of the result of the Function that will be stored by this weak reference.</typeparam>
    public class WeakFunction<TResult>
    {
        #region Class Variables
#if SILVERLIGHT
        /// <summary>The function</summary>
        private Func<TResult> function;
#endif
        /// <summary>The static function</summary>
        private Func<TResult> staticFunction;
        #endregion Class Variables

        #region Properties
        #region Public Properties
        /// <summary>Gets a value indicating whether the WeakFunction [is static] or not.</summary>
        /// <value><c>true</c> if [is static]; otherwise, <c>false</c>.</value>
        public bool IsStatic
        {
            get
            {
#if SILVERLIGHT
                return (function != null && function.Target == null) || staticFunction != null;
#else
                return staticFunction != null;
#endif
            }
        }

        /// <summary>Gets the Func's owner. This object is stored as a <see cref="WeakReference"/> target.</summary>
        /// <value>The target.</value>
        public object Target
        {
            get
            {
                if (Reference == null)
                    return null;

                return Reference.Target;
            }
        }

        #region Public Virtual Properties
        /// <summary>Gets a value indicating whether the Functions owner [is alive] still, or if it was collected by the Garbage Collector already.</summary>
        /// <value><c>true</c> if [is alive]; otherwise, <c>false</c>.</value>
        public virtual bool IsAlive
        {
            get
            {
                if (this.staticFunction == null && Reference == null)
                    return false;

                if (this.staticFunction != null)
                {
                    if (Reference != null)
                        return Reference.IsAlive;

                    return true;
                }
                return Reference.IsAlive;
            }
        }

        /// <summary>Gets the name of the method that the WeakFunction represents.</summary>
        /// <value>The name of the method.</value>
        public virtual string MethodName
        {
            get
            {
                if (staticFunction != null)
                {
#if NETFX_CORE
                    return staticFunction.GetMethodInfo().Name;
#else
                    return staticFunction.Method.Name;
#endif
                }

#if SILVERLIGHT
                if (function != null)
                    return function.Method.Name;

                if (Method != null)
                    return Method.Name;

                return string.Empty;
#else
                return Method.Name;
#endif
            }
        }
        #endregion Public Virtual Properties
        #endregion Public Properties

        #region Protected Properties
        /// <summary>Gets or sets the <see cref="MethodInfo"/> corresponding to this WeakFunction's method passed in the constructor.</summary>
        /// <value>The method.</value>
        protected MethodInfo Method { get; set; }

        /// <summary>Gets or sets a WeakReference to this WakFunction's action's target. This is not necessarily the same as <see cref="Reference"/>, for example if the method is anonymous.</summary>
        /// <value>The function reference.</value>
        protected WeakReference FunctionReference { get; set; }

        /// <summary>
        /// Gets or sets a WeakReference to the target passed when constructing the WeakFunction.
        /// This is not necessarily the same as <see cref="FunctionReference"/>, for example if the method is anonymous.
        /// </summary>
        /// <value>The reference.</value>
        protected WeakReference Reference { get; set; }

        /// <summary>Gets the owner of the Function that was passed as a parameter. This is not necessarily the same as <see cref="Target"/>, for example if the method is anonymous.</summary>
        /// <value>The function target.</value>
        protected object FunctionTarget
        {
            get
            {
                if (FunctionReference == null)
                    return null;

                return FunctionReference.Target;
            }
        }
        #endregion Protected Properties
        #endregion Properties

        #region Constructors
        /// <summary>Initializes a new instance of the <see cref="WeakFunction{TResult}"/> class.</summary>
        protected WeakFunction() { }

        /// <summary>Initializes a new instance of the <see cref="WeakFunction{TResult}"/> class.</summary>
        /// <param name="function">The Func that will be associated to this isntance.</param>
        public WeakFunction(Func<TResult> function) : this(function == null ? null : function.Target, function) { }

        /// <summary>Initializes a new instance of the <see cref="WeakFunction{TResult}"/> class.</summary>
        /// <param name="target">The Function's owner</param>
        /// <param name="function">The Function that will be associated to this instance.</param>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Method should fail with an exception of function is null.")]
        public WeakFunction(object target, Func<TResult> function)
        {
#if NETFX_CORE
            if (function.GetMethodInfo().IsStatic)
#else
            if (function.Method.IsStatic)
#endif
            {
                staticFunction = function;
                if (target != null)
                {
                    // Keep a reference to the target to control the WeakAction's lifetime.
                    Reference = new WeakReference(target);
                }
                return;
            }

#if SILVERLIGHT
            if (!function.Method.IsPublic || (target != null && !target.GetType().IsPublic && !target.GetType().IsNestedPublic))
                this.function = function;
            else
            {
                var name = function.Method.Name;
                if (name.Contains("<") && name.Contains(">"))
                {
                    //Anonymous method
                    this.function = function;
                }
                else
                {
                    Method = function.Method;
                    FunctionReference = new WeakReference(function.Target);
                }
            }
#else
#if NETFX_CORE
            Method = function.GetMethodInfo();
#else
            Method = function.Method;
#endif
            FunctionReference = new WeakReference(function.Target);
#endif
            Reference = new WeakReference(target);
        }
        #endregion Constructors

        #region Public Functions
        /// <summary>Executes the action. This only happens if the Function's owner is still alive.</summary>
        /// <returns>The result of the Function stored as reference.</returns>
        public TResult Execute()
        {
            if (staticFunction != null)
                return staticFunction();

            var functionTarget = FunctionTarget;
            if (IsAlive)
            {
                if (Method != null && FunctionReference != null && functionTarget != null)
                    return (TResult)Method.Invoke(functionTarget, null);

#if SILVERLIGHT
                if (function != null)
                    return function();
#endif
            }
            return default(TResult);
        }

        /// <summary>Sets the reference that this instance stores to null.</summary>
        public void MarkForDeletion()
        {
            Reference = null;
            FunctionReference = null;
            Method = null;
            staticFunction = null;
#if SILVERLIGHT
            function = null;
#endif
        }
        #endregion Public Functions
    }
}
